﻿//-----------------------------------------------------------------------
// <copyright file="MembershipService.cs" company="OOHM PROCESSAMENTO DE DADOS LTDA.">
//     Microsoft Public License (MS-PL) This license governs use of the accompanying
//     software. If you use the software, you accept this license. If you do not
//     accept the license, do not use the software. 1. Definitions The terms "reproduce,"
//     "reproduction," "derivative works," and "distribution" have the same meaning
//     here as under U.S. copyright law. A "contribution" is the original software,
//     or any additions or changes to the software. A "contributor" is any person
//     that distributes its contribution under this license. "Licensed patents"
//     are a contributor's patent claims that read directly on its contribution.
//     2. Grant of Rights (A) Copyright Grant- Subject to the terms of this license,
//     including the license conditions and limitations in section 3, each contributor
//     grants you a non-exclusive, worldwide, royalty-free copyright license to
//     reproduce its contribution, prepare derivative works of its contribution,
//     and distribute its contribution or any derivative works that you create.
//     (B) Patent Grant- Subject to the terms of this license, including the license
//     conditions and limitations in section 3, each contributor grants you a non-exclusive,
//     worldwide, royalty-free license under its licensed patents to make, have
//     made, use, sell, offer for sale, import, and/or otherwise dispose of its
//     contribution in the software or derivative works of the contribution in
//     the software. 3. Conditions and Limitations (A) No Trademark License- This
//     license does not grant you rights to use any contributors' name, logo, or
//     trademarks. (B) If you bring a patent claim against any contributor over
//     patents that you claim are infringed by the software, your patent license
//     from such contributor to the software ends automatically. (C) If you distribute
//     any portion of the software, you must retain all copyright, patent, trademark,
//     and attribution notices that are present in the software. (D) If you distribute
//     any portion of the software in source code form, you may do so only under
//     this license by including a complete copy of this license with your distribution.
//     If you distribute any portion of the software in compiled or object code
//     form, you may only do so under a license that complies with this license.
//     (E) The software is licensed "as-is." You bear the risk of using it. The
//     contributors give no express warranties, guarantees or conditions. You may
//     have additional consumer rights under your local laws which this license
//     cannot change. To the extent permitted under your local laws, the contributors
//     exclude the implied warranties of merchantability, fitness for a particular
// </copyright>
//-----------------------------------------------------------------------
namespace AbstractSaaSTemplate.Application.Services{    using System;
    using System.Globalization;
    using AbstractSaaSTemplate.Application.Configuration;
    using AbstractSaaSTemplate.Application.Localization;
    using AbstractSaaSTemplate.Domain.Models.Entities;
    using AbstractSaaSTemplate.Domain.Models.Enumerators;
    using AbstractSaaSTemplate.Domain.Repositories;
    using AbstractSaaSTemplate.Domain.Services;
    using OOHM.Framework.Configuration;

    /// <summary>
    /// Implements 'IMembershipService' and all its contract, providing
    /// intelligent services to 'Membership' concern of the business.
    /// </summary>
    public sealed class MembershipService : IMembershipService
    {
        /// <summary>
        /// Initializes a new instance of the MembershipService class.
        /// </summary>
        /// <param name="repositoryForMembership">The repository to operate the aggregate 'Membership'.</param>
        /// <param name="repositoryForSessions">The repository to operate the aggregate 'Sessions'.</param>
        public MembershipService(
            IMembershipRepository repositoryForMembership,
            ISessionRepository repositoryForSessions)
        {
            this.RepositoryForMembership = repositoryForMembership;
            this.RepositoryForSessions = repositoryForSessions;
        }

        /// <summary>
        /// Gets the repository to operate the aggregate 'Membership'
        /// </summary>
        public IMembershipRepository RepositoryForMembership { get; private set; }

        /// <summary>
        /// Gets the repository to operate the aggregate 'Sessions'
        /// </summary>
        public ISessionRepository RepositoryForSessions { get; private set; }

        /// <summary>
        /// Gets the minimum length for passwords in the system.
        /// </summary>
        public int MinPasswordLength
        {
            get
            {
                const int FailOverPasswordLength = 6;
                var configValue = Configurator.GetConfigurationByKey<string>(ConfigurationKeys.MinPasswordLength);

                var value = default(int);
                if (int.TryParse(configValue, out value))
                {
                    return FailOverPasswordLength;
                }

                return value;
            }
        }

        /// <summary>
        /// Gets the active member in the system
        /// </summary>
        public User ActiveUser
        {
            get
            {
                var activeSession = this.RepositoryForSessions.ActiveSession;
                if (activeSession == null)
                {
                    return null;
                }

                return this.RepositoryForMembership.GetUserByCredentials(activeSession.OwnerLogin);
            }
        }

        /// <summary>
        /// Generates a random password and retrieves it.
        /// </summary>
        /// <returns>Returns a string with a new password.</returns>
        public string GenerateNewPassword()
        {
            return Guid.NewGuid()
                .ToString()
                .Substring(0, 6);
        }

        /// <summary>
        /// Checks if the credentials provided are valid in the system.
        /// </summary>
        /// <param name="login">The login to be checked</param>
        /// <param name="password">The password to be checked</param>
        /// <returns>Returns true if the credentials are valid; otherwise returns false.</returns>
        public bool CheckCredentials(
            string login,
            string password)
        {
            if (string.IsNullOrWhiteSpace(login))
            {
                throw new ArgumentNullException("login");
            }

            if (string.IsNullOrWhiteSpace(password))
            {
                throw new ArgumentNullException("password");
            }

            var member = this.RepositoryForMembership.GetUserByCredentials(login, password);
            if (member == null)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Checks the credentials and, if valid, performs the sign-in
        /// creating a session which can be used by the user to acccess
        /// the system.
        /// </summary>
        /// <param name="login">The login which will be checked and, if credentials pass, will be used to create the session.</param>
        /// <param name="password">The password which will be checked.</param>
        /// <param name="rememberMe">A value indicating whether a persistent session will be created or not</param>
        /// <returns>Returns true if credentials pass; otherwise returns false.</returns>
        public bool LogOn(
            string login,
            string password,
            bool rememberMe)
        {
            if (!this.CheckCredentials(login, password))
            {
                return false;
            }

            var member = this.RepositoryForMembership.GetUserByCredentials(login);
            
            // create the session basics
            // so taht we can add it to the session repository
            var session = new Session()
            {
                OwnerLogin = login,
                Culture = member.Culture,
                IsPersistent = rememberMe
            };

            this.RepositoryForSessions.AddOrUpdateSession(session);
            return true;
        }

        /// <summary>
        /// Destroys the session and stop user from being able to use the system.
        /// </summary>
        public void LogOut()
        {
            var activeSession = this.RepositoryForSessions.ActiveSession;

            if (activeSession != null)
            {
                this.RepositoryForSessions.DeleteSession(activeSession);
            }
        }

        /// <summary>
        /// Checks if the credentials (login and oldPassword) are valid.
        /// If valid, changes credentials for a particular member
        /// </summary>
        /// <param name="login">The credential's login</param>
        /// <param name="oldPassword">The old password used to validate the credentials before changing it.</param>
        /// <param name="newPassword">The new password which will be set to the credentials.</param>
        /// <returns>Returns true if the credentials are valid and could be changed; otherwise returns false.</returns>
        public bool ChangeCredentials(
            string login,
            string oldPassword,
            string newPassword)
        {
            if (string.IsNullOrWhiteSpace(login))
            {
                throw new ArgumentNullException("login");
            }

            if (string.IsNullOrEmpty(oldPassword))
            {
                throw new ArgumentException(
                    "Value cannot be null or empty.",
                    "oldPassword");
            }

            if (string.IsNullOrEmpty(newPassword))
            {
                throw new ArgumentException(
                    "Value cannot be null or empty.",
                    "newPassword");
            }

            var member = this.RepositoryForMembership.GetUserByCredentials(
                login,
                oldPassword);

            if (member == null)
            {
                return false;
            }

            this.RepositoryForMembership.AddOrUpdateUser(member);
            return true;
        }

        /// <summary>
        /// Changes the culture for one member into
        /// his/her profile so that it's retrieved correctly
        /// after the next log-on.
        /// </summary>
        /// <param name="login">The login of the user. If null or white space, we don't save the changes to the member, only changing the current session's culture.</param>
        /// <param name="newCultureName">The new culture to be used for this member</param>
        public void ChangeCulture(
            string login,
            string newCultureName)
        {
            // first we will check if the culture exists
            // if not, we forward the exception adding some
            // more detailed information.
            var cultureInfo = null as CultureInfo;
            try
            {
                cultureInfo = new CultureInfo(newCultureName);
            }
            catch (ArgumentException innerException)
            {
                throw new ArgumentOutOfRangeException(
                    Messages.TheCultureDefinedIsInvalid,
                    innerException);
            }

            // if tehre is no culture info,
            // we give up on changing this
            if (cultureInfo == null)
            {
                return;
            }

            // then we retrieve the member and the session
            // the member is "optional". We don't really need
            // to save the members' preferences unless he is authenticated
            // in that case, we DO need to save his/her preferences
            // Administrators can also change the member's culture
            // so we try to match the member's login and, if matched,
            // we try to change this member's culture.
            var member = null as User;
            if (!string.IsNullOrWhiteSpace(login))
            {
                member = this.RepositoryForMembership.GetUserByCredentials(login);
            }

            // if we find a member, we update it
            if (member != null)
            {
                member.Culture = cultureInfo;
                this.RepositoryForMembership.AddOrUpdateUser(member);
            }

            // if the member for which the session is culture is being
            // change matches the active session, we may change the
            // current session's culture. Otherwise, only on the next
            // logon the new culture should be applied to the user's
            // session. Otherwise we could cause major security problems
            // by allowing someone access someone else's session state.
            // to start, we check if there's any active session.
            var activeSession = this.RepositoryForSessions.ActiveSession;
            if (activeSession == null)
            {
                return;
            }
            
            // if we recognised a user and it is a valid user
            // we only change the current session's culture
            // in case the user for which we are changing and the current user
            // are the same.
            if (member != null &&
                string.Compare(activeSession.OwnerLogin, member.Credentials.Login, false, cultureInfo) != 0)
            {
                return;
            }

            // if no member is authenticated or if the member
            // for whom the culture is being changed maches
            // the current session's member, we change the culture
            // of the session, causing the UI to change it's culture.
            activeSession.Culture = cultureInfo;
            this.RepositoryForSessions.AddOrUpdateSession(activeSession);
        }

        /// <summary>
        /// Checks if a role has access to a particular feature.
        /// </summary>
        /// <param name="accessLevel">The access level of the role and feature.</param>
        /// <param name="role">The role which will be checked.</param>
        /// <param name="feature">The feature which is being accessed.</param>
        /// <returns>Returns true if the role is authorised for the feature. Otherwise returns false.</returns>
        public bool HasRoleAccessToFeature(
            AccessLevels accessLevel,
            AccessRoles role,
            ControlledFeatures feature)
        {
            var levelNames = Enum.GetNames(typeof(AccessLevels));
            foreach (var levelName in levelNames)
            {
                var level = AccessLevels.None;
                if (!Enum.TryParse<AccessLevels>(levelName, out level))
                {
                    continue;
                }

                if (level == AccessLevels.None)
                {
                    continue;
                }

                var association = this.RepositoryForMembership.GetRoleFeatureAssociation(
                    level,
                    role,
                    feature);

                if (association != null)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Checks if a user is member of a particular role
        /// </summary>
        /// <param name="role">The role against we will check if the user is contained.</param>
        /// <returns>Returns true if the user is part of the role; otherwise returns false.</returns>
        public bool IsActiveUserInRole(AccessRoles role)
        {
            var userRole = this.RepositoryForMembership.GetUserRoleAssociation(
                this.ActiveUser,
                role);

            return userRole != null;
        }
    }
}


